﻿using System;
using System.Diagnostics;
using System.IO;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SunXNA.RainbowXNA
{
    class CHeightMap
    {
        #region Public Types
        #endregion

        #region Private Member Variables

        private int m_width;
        private int m_depth;
        private float m_maxHeight;
        private float m_minHeight;
        private float[] m_heightPercentages;

        #endregion

        #region Protected Member Variables
        #endregion

        #region Puplic Member Properties

        public int Width
        {
            get
            {
                return m_width;
            }
        }

        public int Depth
        {
            get
            {
                return m_depth;
            }
        }

        public float MaxHeight
        {
            get
            {
                return m_maxHeight;
            }
        }

        public float MinHeight
        {
            get
            {
                return m_minHeight;
            }
        }

        #endregion

        #region Public Member Functions

        public CHeightMap(int width, int depth, float minHeight, float maxHeight)
        {
            m_width = width;
            m_depth = depth;
            m_maxHeight = maxHeight;
            m_minHeight = minHeight;

            m_heightPercentages = new float[m_width * m_depth];
        }

        /// <summary>
        /// Gets the height value at a partciular vertex on the Map. 0,0 is the very top left vertex of the world. [i.e. x = 0 is the Value most toward Vector3.Left and z = 0 is the value most toward Vector3.Forward ]
        /// </summary>
        /// <param name="x"></param>
        /// <param name="z"></param>
        /// <returns>The height of the terrain</returns>
        public float GetHeightValue(int x, int z)
        {
            Trace.Assert(x >= 0 && x < m_width);
            Trace.Assert(z >= 0 && z < m_depth);
            return m_minHeight + GetHeightPercentage(x,z) * (m_maxHeight - m_minHeight);
        }

        /// <summary>
        /// Gets the height value at a partciular vertex on the Map. 0,0 is the very top left vertex of the world. [i.e. x = 0 is the Value most toward Vector3.Left and z = 0 is the value most toward Vector3.Forward ]
        /// </summary>
        /// <param name="x"></param>
        /// <param name="z"></param>
        /// <returns>A value between 0 and 1 representing a percentage of the height value between MinHeight and MaxHeight</returns>
        public float GetHeightPercentage(int x, int z)
        {
            Trace.Assert(x >= 0 && x < m_width);
            Trace.Assert(z >= 0 && z < m_depth);
            return m_heightPercentages[x + z * m_width];
        }

        public void GenerateRandomHeightmap()
        {
            Random random = new Random();

            for (int i = 0; i < m_heightPercentages.Length; ++i)
            {
                m_heightPercentages[i] = (float)random.NextDouble();
            }
        }

        /// <summary>
        /// Gets the height value at a partciular vertex on the Map. 0,0 is the very top left vertex of the world. [i.e. x = 0 is the Value most toward Vector3.Left and z = 0 is the value most toward Vector3.Forward ]
        /// </summary>
        /// <param name="x"></param>
        /// <param name="z"></param>
        /// <param name="cellWidth"></param>
        /// <param name="cellHeight"></param>
        /// <returns>The normal of the requested point</returns>
        public Vector3 GetNormal(int x, int z, float cellWidth, float cellHeight)
        {
            Trace.Assert(x >= 0 && x < m_width);
            Trace.Assert(z >= 0 && z < m_depth);

            Vector3 v3CumulativeNormal = Vector3.Zero;
            Vector3 v3Me = new Vector3(0.0f, GetHeightValue(x, z), 0.0f);

            if (z > 0)
            {
                //this vertex is on bottom of a triangle

                if (x > 0)
                {
                    //this vertex is the bottom right of a triangle

                    Vector3 v3TopRight = new Vector3(0.0f, GetHeightValue(x, z - 1), -cellHeight);
                    Vector3 v3BottomLeft = new Vector3(-cellWidth, GetHeightValue(x - 1, z), 0.0f);

                    Vector3 v3TRtoBL = v3BottomLeft - v3TopRight;
                    Vector3 v3BRtoBL = v3BottomLeft - v3Me;

                    v3CumulativeNormal += Vector3.Cross(v3BRtoBL, v3TRtoBL);
                    Trace.Assert(Vector3.Dot(v3CumulativeNormal, Vector3.Up) > 0.0f);
                }

                if (x < Width - 1)
                {
                    //this vertex is the bottom left of 2 triangles

                    Vector3 v3TopRight = new Vector3(cellWidth, GetHeightValue(x + 1, z - 1), -cellHeight);

                    {
                        Vector3 v3TopLeft = new Vector3(0.0f, GetHeightValue(x, z - 1), -cellHeight);

                        Vector3 v3TRtoTL = v3TopLeft - v3TopRight;
                        Vector3 v3BLtoTL = v3TopLeft - v3Me;

                        v3CumulativeNormal += Vector3.Cross(v3BLtoTL, v3TRtoTL);
                        Trace.Assert(Vector3.Dot(v3CumulativeNormal, Vector3.Up) > 0.0f);
                    }

                    {
                        Vector3 v3BottomRight = new Vector3(cellWidth, GetHeightValue(x + 1, z), 0.0f);

                        Vector3 v3TRtoBL = v3Me - v3TopRight;
                        Vector3 v3BRtoBL = v3Me - v3BottomRight;

                        v3CumulativeNormal += Vector3.Cross(v3BRtoBL, v3TRtoBL);
                        Trace.Assert(Vector3.Dot(v3CumulativeNormal, Vector3.Up) > 0.0f);
                    }
                }
            }

            if (z < Depth - 1)
            {
                //this vertex is on the top of a triangle

                if (x > 0)
                {
                    //this triangle is on the top right of 2 triangles

                    Vector3 v3BottomLeft = new Vector3(-cellWidth, GetHeightValue(x - 1, z + 1), cellHeight);

                    {
                        Vector3 v3BottomRight = new Vector3(0.0f, GetHeightValue(x, z + 1), cellHeight);

                        Vector3 v3TRtoBL = v3BottomLeft - v3Me;
                        Vector3 v3BRtoBL = v3BottomLeft - v3BottomRight;

                        v3CumulativeNormal += Vector3.Cross(v3BRtoBL, v3TRtoBL);
                        Trace.Assert(Vector3.Dot(v3CumulativeNormal, Vector3.Up) > 0.0f);
                    }

                    {
                        Vector3 v3TopLeft = new Vector3(-cellWidth, GetHeightValue(x - 1, z), 0.0f);

                        Vector3 v3TRtoTL = v3TopLeft - v3Me;
                        Vector3 v3BLtoTL = v3TopLeft - v3BottomLeft;

                        v3CumulativeNormal += Vector3.Cross(v3BLtoTL, v3TRtoTL);
                        Trace.Assert(Vector3.Dot(v3CumulativeNormal, Vector3.Up) > 0.0f);
                    }
                }

                if (x < Width - 1)
                {
                    //this triangle is on the top left of a triangle

                    Vector3 v3BottomLeft = new Vector3(-cellWidth, GetHeightValue(x , z + 1), cellHeight);
                    Vector3 v3TopRight = new Vector3(cellWidth, GetHeightValue(x + 1, z), 0.0f);

                    Vector3 v3TRtoTL = v3Me - v3TopRight;
                    Vector3 v3BLtoTL = v3Me - v3BottomLeft;

                    v3CumulativeNormal += Vector3.Cross(v3BLtoTL, v3TRtoTL);
                    Trace.Assert(Vector3.Dot(v3CumulativeNormal, Vector3.Up) > 0.0f);
                }
            }

            return Vector3.Normalize(v3CumulativeNormal);
        }

        public static CHeightMap LoadFromFile(Texture2D heightImage, float minHeight, float maxHeight)
        {
            CHeightMap newHeightMap = null;

            int width = heightImage.Width;
            int depth = heightImage.Height;

            newHeightMap = new CHeightMap(width, depth, minHeight, maxHeight);

            Color[] textureData = new Color[width * depth];
            heightImage.GetData(textureData);

            for (int i = 0; i < textureData.Length; ++i)
            {
                float scale = ((float)textureData[i].R) / ((float)byte.MaxValue)
                              + ((float)textureData[i].G) / ((float)byte.MaxValue)
                              + ((float)textureData[i].B) / ((float)byte.MaxValue);

                scale /= 3.0f;

                newHeightMap.m_heightPercentages[i] = scale;
            }


            return newHeightMap;
        }

        #endregion

        #region Protected Member Functions
        #endregion

        #region Private Member Functions
        #endregion
    }
}
